﻿using System;
using System.Collections.Generic;
using System.Configuration;
using DNT.RavenQA.Core.Extensions;
using DNT.RavenQA.Core.Models;
using DNT.RavenQA.Web.Infrastructure.Indexing;
using DNT.RavenQA.Web.Infrastructure.IoC;
using Raven.Client;
using Raven.Client.Document;
using Raven.Client.Indexes;
using Raven.Client.Shard;

namespace DNT.RavenQA.Web.Infrastructure.Registry
{
    public static class DocumentStoreFactory
    {
        public static IDocumentStore Build()
        {
            var section = ConfigurationManager.GetSection("connectionStrings") as ConnectionStringsSection;
            var configSource = section.SectionInformation.ConfigSource;
            IDocumentStore documentStore = null;

            if (configSource.IsNull())
                throw new ConfigurationErrorsException("Convention needs you specify a value for 'configSource' attribute.");

            if (configSource.IndexOf("Default.config", StringComparison.OrdinalIgnoreCase) >= 0)
                documentStore = BuildDocumentStore(section.ConnectionStrings);

            if (configSource.IndexOf("Sharded.config", StringComparison.OrdinalIgnoreCase) >= 0)
                documentStore = BuildShardedDocumentStore(section.ConnectionStrings);

            if (documentStore.IsNull())
                throw new NotSupportedException("Please, choose between 'Default.config' or 'Sharded.config' values for configSource attribute.");

            documentStore.Initialize();
            CreateIndexes(documentStore);
            return documentStore;
        }

        private static void CreateIndexes(ShardedDocumentStore documentStore)
        {
            foreach (var store in documentStore.ShardStrategy.Shards)
                IndexCreation.CreateIndexes(typeof(QuestionsTagListIndex).Assembly, store.Value);
        }

        private static void CreateIndexes(IDocumentStore documentStore)
        {
            if (documentStore is ShardedDocumentStore)
                CreateIndexes((ShardedDocumentStore) documentStore);
            else
                IndexCreation.CreateIndexes(typeof(QuestionsTagListIndex).Assembly, documentStore);
        }

        private static IDocumentStore BuildDocumentStore(ConnectionStringSettingsCollection connectionStrings)
        {
            var connectionString = ReadConnectionStringFromConfiguration(connectionStrings);

            if (connectionString.IsNull())
                throw new BootstrapException("You need to configure a connection string named 'default' or '[MachineName]_default'.");

            var documentStore = BuildDocumentStore(connectionString.Name);
            DocumentConventionBuilder.SetConventions(documentStore.Conventions);
            return documentStore;
        }

        private static IDocumentStore BuildDocumentStore(String connectionStringName)
        {
            return new DocumentStore { ConnectionStringName = connectionStringName };
        }

        private static ConnectionStringSettings ReadConnectionStringFromConfiguration(ConnectionStringSettingsCollection connectionStrings)
        {
            var connectionStringSettingsKey = String.Format("{0}_default", Environment.MachineName);
            var connectionStringSettings = connectionStrings[connectionStringSettingsKey];

            if (connectionStringSettings.IsNotNull())
                return connectionStringSettings;

            connectionStringSettingsKey = "default";
            connectionStringSettings = connectionStrings[connectionStringSettingsKey];

            return connectionStringSettings;
        }

        private static IDocumentStore BuildShardedDocumentStore(ConnectionStringSettingsCollection connectionStrings)
        {
            var shards = new Dictionary<String, IDocumentStore>();

            for (var i = 0; i < connectionStrings.Count; i++)
                shards.Add(connectionStrings[i].Name, BuildDocumentStore(connectionStrings[i].Name));

            var strategy = new ShardStrategy(shards)
                               {
                                   ShardAccessStrategy = new ParallelShardAccessStrategy()
                               }.ShardingOn<Question>(q => q.Id)
                .ShardingOn<Answer>(q => q.QuestionId)
                .ShardingOn<ContributeViews>(q => q.QuestionId)
                .ShardingOn<ContributeVotes>(q => q.ContributeId)
                .ShardingOn<User>(q => q.Id);

            DocumentConventionBuilder.SetConventions(strategy.Conventions);
            strategy.ShardResolutionStrategy = new DefaultShardResolutionStrategy(shards.Keys, strategy);
            
            return new ShardedDocumentStore(strategy);
        }
    }
}